#include "YXMySQL.h"

#include <assert.h>

#include "../../include/mysql/mysql.h"
#include "../common/LinkedList.h"
#include "YXConstant.h"
#include "YXLog.h"

#include <stdio.h>
#include <stdlib.h>

DataSource *createDataSource(char *host, int port, char *user, char *password, char *database) {
    DataSource *ds = malloc(sizeof(DataSource));
    pthread_mutex_init(&ds->lock, NULL);
    pthread_cond_init(&ds->cond, NULL);
    LinkedList *connections = linked_list_create();
    ds->connections         = connections;
    ds->host                = host;
    ds->port                = port;
    ds->user                = user;
    ds->password            = password;
    ds->database            = database;

    // 初始化连接
    addConnection(ds);

    return ds;
}

int addConnection(DataSource *ds) {
    Connection *conn = mysql_init(NULL);
    if (mysql_real_connect(conn, ds->host, ds->user, ds->password, ds->database, ds->port, NULL, 0) == NULL) {
        logError("连接MySQL失败：%s", mysql_error(conn));
        return YX_ERR;
    }

    pthread_mutex_lock(&ds->lock);
    linked_list_add_r(ds->connections, conn);
    pthread_cond_signal(&ds->cond);
    pthread_mutex_unlock(&ds->lock);
    return YX_OK;
}

Connection *getConnection(DataSource *ds) {
    pthread_mutex_lock(&ds->lock);
    while (linked_list_is_empty(ds->connections)) {
        pthread_cond_wait(&ds->cond, &ds->lock);
    }
    Connection *connection = linked_list_pop_r(ds->connections);
    pthread_mutex_unlock(&ds->lock);

    return connection;
}

void returnConnection(DataSource *ds, Connection *connection) {
    pthread_mutex_lock(&ds->lock);
    linked_list_add_r(ds->connections, connection);
    pthread_cond_signal(&ds->cond);
    pthread_mutex_unlock(&ds->lock);
}

void closeConnection(Connection *connection) { mysql_close(connection); }

static MYSQLResult *handleMYSQLResult(MYSQL_RES *mysql_res) {
    // 转换数据
    if (mysql_num_rows(mysql_res) == 0) {
        return createMYSQLSuccessResult(NULL);
    }
    if (mysql_num_fields(mysql_res) == 1) {
        if (mysql_num_rows(mysql_res) == 1) {
            cJSON *result = NULL;
            int    type   = mysql_fetch_field(mysql_res)->type;
            if (type == FIELD_TYPE_TINY || type == FIELD_TYPE_SHORT || type == FIELD_TYPE_LONG ||
                type == FIELD_TYPE_INT24 || type == FIELD_TYPE_LONGLONG) {
                result = cJSON_CreateNumber(atoi(mysql_fetch_row(mysql_res)[0]));
            } else if (type == FIELD_TYPE_DECIMAL || type == FIELD_TYPE_FLOAT || type == FIELD_TYPE_DOUBLE) {
                result = cJSON_CreateNumber(atof(mysql_fetch_row(mysql_res)[0]));
            } else if (type == FIELD_TYPE_STRING || type == FIELD_TYPE_VAR_STRING) {
                result = cJSON_CreateString(mysql_fetch_row(mysql_res)[0]);
            }
            mysql_free_result(mysql_res);
            return createMYSQLSuccessResult(result);
        } else {
            cJSON *list_result = cJSON_CreateArray();
            int    type        = mysql_fetch_field(mysql_res)->type;
            for (int i = 0; i < mysql_num_rows(mysql_res); i++) {
                if (type == FIELD_TYPE_TINY || type == FIELD_TYPE_SHORT || type == FIELD_TYPE_LONG ||
                    type == FIELD_TYPE_INT24 || type == FIELD_TYPE_LONGLONG) {
                    cJSON_AddItemToArray(list_result, cJSON_CreateNumber(atoi(mysql_fetch_row(mysql_res)[0])));
                } else if (type == FIELD_TYPE_DECIMAL || type == FIELD_TYPE_FLOAT || type == FIELD_TYPE_DOUBLE) {
                    cJSON_AddItemToArray(list_result, cJSON_CreateNumber(atof(mysql_fetch_row(mysql_res)[0])));
                } else if (type == FIELD_TYPE_STRING || type == FIELD_TYPE_VAR_STRING) {
                    cJSON_AddItemToArray(list_result, cJSON_CreateString(mysql_fetch_row(mysql_res)[0]));
                }
            }
            mysql_free_result(mysql_res);
            return createMYSQLSuccessResult(list_result);
        }
    }
    if (mysql_num_fields(mysql_res) > 1) {
        if (mysql_num_rows(mysql_res) == 1) {
            cJSON    *result = cJSON_CreateObject();
            MYSQL_ROW row    = mysql_fetch_row(mysql_res);
            for (int j = 0; j < mysql_num_fields(mysql_res); j++) {
                MYSQL_FIELD *field = mysql_fetch_field(mysql_res);
                int          type  = field->type;
                if (type == FIELD_TYPE_TINY || type == FIELD_TYPE_SHORT || type == FIELD_TYPE_LONG ||
                    type == FIELD_TYPE_INT24 || type == FIELD_TYPE_LONGLONG) {
                    cJSON_AddNumberToObject(result, field->name, atoi(row[j]));
                } else if (type == FIELD_TYPE_DECIMAL || type == FIELD_TYPE_FLOAT || type == FIELD_TYPE_DOUBLE) {
                    cJSON_AddNumberToObject(result, field->name, atof(row[j]));
                } else if (type == FIELD_TYPE_STRING || type == FIELD_TYPE_VAR_STRING) {
                    cJSON_AddStringToObject(result, field->name, row[j]);
                } else if (type == FIELD_TYPE_DATETIME) {
                    cJSON_AddStringToObject(result, field->name, row[j]);
                }
            }
            mysql_free_result(mysql_res);
            return createMYSQLSuccessResult(result);
        } else {
            cJSON *result = cJSON_CreateArray();
            for (int i = 0; i < mysql_num_rows(mysql_res); i++) {
                cJSON    *item = cJSON_CreateObject();
                MYSQL_ROW row  = mysql_fetch_row(mysql_res);
                for (int j = 0; j < mysql_num_fields(mysql_res); j++) {
                    MYSQL_FIELD *field = mysql_fetch_field(mysql_res);
                    int          type  = field->type;
                    if (type == FIELD_TYPE_TINY || type == FIELD_TYPE_SHORT || type == FIELD_TYPE_LONG ||
                        type == FIELD_TYPE_INT24 || type == FIELD_TYPE_LONGLONG) {
                        cJSON_AddNumberToObject(item, field->name, atoi(row[j]));
                    } else if (type == FIELD_TYPE_DECIMAL || type == FIELD_TYPE_FLOAT || type == FIELD_TYPE_DOUBLE) {
                        cJSON_AddNumberToObject(item, field->name, atof(row[j]));
                    } else if (type == FIELD_TYPE_STRING || type == FIELD_TYPE_VAR_STRING) {
                        cJSON_AddStringToObject(item, field->name, row[j]);
                    } else if (type == FIELD_TYPE_DATETIME) {
                        cJSON_AddStringToObject(item, field->name, row[j]);
                    }
                }
                cJSON_AddItemToArray(result, item);
                mysql_field_seek(mysql_res, 0);
            }
            mysql_free_result(mysql_res);
            return createMYSQLSuccessResult(result);
        }
    }
    logFatal("其它情况暂时不支持！");
}

void *exceuteQuery(Connection *connection, char *sql) {
    // 执行查询
    if (mysql_query(connection, sql)) {
        return createMYSQLErrResult(mysql_error(connection));
    }
    // 获取结果
    MYSQL_RES *mysql_res = mysql_store_result(connection);
    if (mysql_res == NULL) {
        return createMYSQLErrResult(mysql_error(connection));
    }
    // 整合数据
    return handleMYSQLResult(mysql_res);
}

void *execute_prepared_statement(MYSQL *connection, const char *sql, MYSQL_BIND *binds) {
    MYSQL_STMT *stmt;

    // 分配预处理语句句柄
    stmt = mysql_stmt_init(connection);

    // 准备SQL语句
    if (mysql_stmt_prepare(stmt, sql, strlen(sql))) {
        const char *err = mysql_stmt_error(stmt);
        mysql_stmt_close(stmt);
        return createMYSQLErrResult(err);
    }

    // 绑定参数
    if (mysql_stmt_bind_param(stmt, binds)) {
        const char *err = mysql_stmt_error(stmt);
        mysql_stmt_close(stmt);
        return createMYSQLErrResult(err);
    }

    // 执行预处理语句
    if (mysql_stmt_execute(stmt)) {
        const char *err = mysql_stmt_error(stmt);
        mysql_stmt_close(stmt);
        return createMYSQLErrResult(err);
    }

    // 获取结果
    if (mysql_stmt_store_result(stmt)) {
        const char *err = mysql_stmt_error(stmt);
        mysql_stmt_close(stmt);
        return createMYSQLErrResult(err);
    }

    // 整合数据
}

MYSQLResult *createMYSQLErrResult(const char *err) {
    MYSQLResult *errResult = malloc(sizeof(MYSQLResult));
    errResult->type        = MYSQL_RESULT_ERROR;
    errResult->value       = NULL;
    errResult->errMsg      = err;

    return errResult;
}

MYSQLResult *createMYSQLSuccessResult(void *result) {
    MYSQLResult *successResult = malloc(sizeof(MYSQLResult));
    successResult->type        = MYSQL_RESULT_SUCCESS;
    successResult->value       = result;

    return successResult;
}

const char *yxMYSQLResultGetErrMsg(MYSQLResult *result) { return result->errMsg; }

char *yxMYSQLResultGetStringValue(MYSQLResult *result) {
    yxObject *obj = result->value;
    return obj->valuestring;
}

double yxMYSQLResultGetDoubleValue(MYSQLResult *result) {
    yxObject *obj = result->value;
    return obj->valuedouble;
}

int yxMYSQLResultGetIntValue(MYSQLResult *result) {
    yxObject *obj = result->value;
    return obj->valueint;
}

yxObject *yxMYSQLResultGetObjectValue(MYSQLResult *result) {
    yxObject *obj = result->value;
    return obj;
}

yxArray *yxMYSQLResultGetArrayValue(MYSQLResult *result) {
    yxArray *obj = result->value;
    return obj;
}

void yxMYSQLResultFree(MYSQLResult *result) {
    if (result->value != NULL) {
        yxJSONFree(result->value);
    }
    free(result);
}

void yxMYSQLResultFreeNotValue(MYSQLResult *result) { free(result); }